/**
 * Setvor project
 */
package setvor.npc.generator;

import java.lang.reflect.*;
import java.util.*;
import java.util.regex.*;

import setvor.npc.*;

/**
 * @author julatec
 *
 */
public final class TypeMapper {

	/**
	 * 
	 * @author julatec
	 *
	 */
	public static final class TypeMap{
		
		/**
		 * The java type of this map.
		 */
		private Class<?> type;
		
		/**
		 * The signature of this map.
		 */
		private String signature;
		
		/**
		 * The native type name.
		 */
		private String nativeName;
		
		/**
		 * The native name space of this class.
		 */
		private String nameSpace;
		
		/**
		 * The JNI type of this class.
		 */
		private String jniType;
		
		/**
		 * String for divide name space and scopes. 
		 */
		public static String nameSpaceSeparator = "::";
		
		/**
		 * String for divide name space and scopes in file names.
		 */
		public static String nameSpaceMethodSeparator = "_";
		
		/**
		 * @param type
		 */
		public TypeMap(Class<?> type) {
			this.type = type;
			this.signature = type.isArray() ? type.getName().replace(".", "/") :  
				"L" + type.getName().replace(".", "/") + ";";
			this.nativeName = type.getSimpleName();
			this.nameSpace = getNameSpace(type);
			this.jniType = type.isArray() ? "jobjectArray" :
				Throwable.class.isAssignableFrom(type) ? "jthrowable" :
				"jobject";
		}
		
		/**
		 * @return the nameSpace
		 */
		public String getNameSpace() {
			return nameSpace;
		}

		/**
		 * @param type
		 * @param signature
		 * @param nativeName
		 */
		public TypeMap(Class<?> type, String signature, String nativeName, String jniType) {
			this(type);
			this.signature = signature;
			this.nativeName = nativeName;
			this.jniType = jniType;
		}
		
		/**
		 * @return the jniType
		 */
		public String getJniType() {
			return jniType;
		}

		/**
		 * @param type
		 * @param signature
		 * @param nativeName
		 */
		public TypeMap(Class<?> type, String nativeName) {
			this(type);
			this.nativeName = nativeName;
		}

		/**
		 * @return the type
		 */
		public Class<?> getType() {
			return type;
		}

		/**
		 * @return the signature
		 */
		public String getSignature() {
			return signature;
		}

		/**
		 * @return the nativeName
		 */
		public String getNativeName() {
			return nativeName;
		}
		
		/**
		 * @return the native enun class Name
		 */
		public String getNativeEnumClassName() {
			return nativeName + "_EnumClass";
		}
		
		
		/**
		 * @return the nameSpace
		 */
		public String getCompleteNativeName() {
			return getNameSpace().isEmpty() ? getNativeName() : 
				getNameSpace() + nameSpaceSeparator + getNativeName();
		}
		
		/**
		 * @return the nameSpace
		 */
		public String getNativeFileName() {
			return getNameSpace().isEmpty() ? getNativeName() : 
				getNameSpace().replaceAll(Pattern.quote(nameSpaceSeparator), nameSpaceMethodSeparator) +
					nameSpaceMethodSeparator + getNativeName();
		}
		
		/**
		 * Get the name space of the class represented by the package.
		 * @param type
		 * @return the name space.
		 */
		protected static String getNameSpace(Class<?> type){
			while(type.isArray())
				type =  type.getComponentType();
			if(type.isPrimitive())
				return "";
			return type.getPackage().getName().replaceAll(Pattern.quote("."), nameSpaceSeparator);
		}
		
		/**
		 * Cast any JNI expression in a native value. 
		 * @param expresion to be casted
		 * @return expression casted.
		 */
		protected String castToNative(String expression){
			if(type.equals(void.class))
				return expression;
			if(type.isPrimitive())
				return "((" + getCompleteNativeName() + ")(" + expression +"))";
			if(type.isEnum())
				return "(" + type.getSimpleName() + ")env->CallIntMethod(obj, " +
					type.getSimpleName()+ "OrdinalMethod)";
			return getCastToNativeFunctionName() + "(env, " + expression + ")";
		}
		
		/**
		 * Cast any native expression in a JNI value. 
		 * @param expression to be casted
		 * @return expression casted.
		 */
		protected String castFromNative(String expression){
			if(type.equals(void.class))
				return expression;
			if(type.isPrimitive())
				return "((" + getJniType() + ")(" + expression +"))";
			if(type.isEnum())
				return type.getSimpleName() + "References[" + expression +"]";
			return getCastFromNativeFunctionName() + "(env, " + expression + ")";
		}
		
		/**
		 * Get the name of the method that cast a complex type to
		 * native value.
		 * @return native method name.
		 */
		protected String getCastToNativeFunctionName(){
			return "castToNative_" + getSuffixName();
		}
		
		/**
		 * Get the name of the method that cast a complex type to
		 * JNI value.
		 * @return native method name.
		 */
		protected String getCastFromNativeFunctionName(){
			return "castFromNative_" + getSuffixName();
		}
		
		/**
		 * This method take the type in this instance and change the
		 * array modifier for "ArrayOf" text.
		 * @return suffix for the class.
		 */
		protected String getSuffixName(){
			String name = "";
			Class<?> type = this.type;
			while(type.isArray()){
				type = type.getComponentType();
				name += "ArrayOf";
			}
			return name + type.getSimpleName();
		}
		
		/**
		 * Generate cast function stub for basic type;
		 * @return
		 */
		protected final String[] getCasToNativeStub(){
			if(type.isPrimitive())
				return new String[0];
			
			Vector<String> lines = new Vector<String>();
			String currentLine;
			if(type.isArray() && type.getComponentType().isPrimitive()){
				String returnTypeName = removeNativePointer();
				currentLine = "static inline " + returnTypeName + " " +
					getCastToNativeFunctionName() + 
					"(JNIEnv* env, " + getJniType() +" jni_array)";
				lines.add(currentLine);
				lines.add("{");
				lines.add("\tint size = env->GetArrayLength(jni_array);");
				lines.add("\t" + returnTypeName + " result = " + " " + returnTypeName + "(size);");
				currentLine = getTypeMap(type.getComponentType()).getJniType() +
					"* buffer = (" + getTypeMap(type.getComponentType()).getJniType() + "*)" + 
					"env->GetPrimitiveArrayCritical(jni_array, NULL);";
				lines.add("\t" + currentLine);
				lines.add("\tfor(int i=0; i<size; result[i] = buffer[i++]);");
				lines.add("\tenv->ReleaseFloatArrayElements(jni_array, buffer, JNI_ABORT);");
				lines.add("\treturn result;");
				lines.add("}");
			}else if(type.equals(String.class)){
				currentLine = "static inline string " +
					this.getCastToNativeFunctionName() +
					"(JNIEnv* env, jstring s)";
				lines.add(currentLine);
				lines.add("{");
				lines.add("\tconst char* buffer=env->GetStringUTFChars(s, NULL);");
				lines.add("\tif(!buffer)");
				lines.add("\t\tthrow 1;");
				lines.add("\tstring result=string(buffer);");
				lines.add("\tenv->ReleaseStringUTFChars(s,buffer);");
				lines.add("\treturn result;");
				lines.add("}");			 
			}else if(type.equals(String[].class)){
				String returnTypeName = removeNativePointer();
				currentLine = "static inline " +  returnTypeName + " " +
					getCastToNativeFunctionName() + 
					"(JNIEnv* env, " + getJniType() +" jni_array)";
				lines.add(currentLine);
				lines.add("{");
				lines.add("\tint size=env->GetArrayLength(jni_array);");
				lines.add("\t" + returnTypeName + " result = " + " " + returnTypeName + "(size);");
				currentLine = getJniType() + " buffer = (" + getJniType() + ")" + 
					"env->GetPrimitiveArrayCritical(jni_array, NULL);";
				lines.add("\t" + currentLine);
				lines.add("\tfor(int i=0; i<size; i++){");
				currentLine = "\tjstring str = (jstring)env->GetObjectArrayElement(jni_array, i);";
				lines.add("\t" + currentLine);
				currentLine = "\tresult[i] = str ? " +
					"new string(" + getTypeMap(String.class).castToNative("str") + ".c_str())" +
					" : NULL;";
				lines.add("\t" + currentLine);
				lines.add("\t}");
				lines.add("\tenv->ReleaseFloatArrayElements(result, buffer, JNI_ABORT);");
				lines.add("\treturn result;");
				lines.add("}");					
			}			
			
			return lines.toArray(new String[lines.size()]);
		}
		
		/**
		 * Generate cast function stub for basic type;
		 * @return
		 */
		protected final String[] getCasFromNativeStub(){
			if(type.isPrimitive())
				return new String[0];
			
			Vector<String> lines = new Vector<String>();
			String currentLine;
			if(type.isArray() && type.getComponentType().isPrimitive()){
				String returnTypeName = getJniType();
				currentLine = "static inline " +  returnTypeName + " " + 
					getCastFromNativeFunctionName() + 
					"(JNIEnv* env, " + removeNativePointer() +" array)";
				lines.add(currentLine);
				lines.add("{");
				lines.add("\tint size=env->array.size();");
				currentLine = returnTypeName + " result = env->New" + 
					capitalize(type.getComponentType().getCanonicalName()) +
					"Array(size);";
				lines.add("\t" + currentLine);
				currentLine = getTypeMap(type.getComponentType()).getJniType() + "* " +
					"buffer = (" + getTypeMap(type.getComponentType()).getJniType() + "*)" + 
					"env->GetPrimitiveArrayCritical(result, NULL);";
				lines.add("\t" + currentLine);
				lines.add("\tfor(int i=0; i<size; buffer[i]=array[i++]);");
				lines.add("\tenv->ReleasePrimitiveArrayCritical(result, buffer, 0);");
				lines.add("\treturn result;");
				lines.add("}");
			}else if(type.equals(String.class)){
				lines.add("static inline jstring castFromNative_String(JNIEnv* env, string* str)");
				lines.add("\treturn env->NewStringUTF(str->c_str());");
				lines.add("}");
			}else if(type.equals(String[].class)){
				String returnTypeName = getJniType();
				currentLine = "static inline " + returnTypeName + 
					getCastToNativeFunctionName() + 
					"(JNIEnv* env, " + removeNativePointer() +" array)";
				lines.add(currentLine);
				lines.add("{");
				lines.add("\tint size=env->array.size();");
				lines.add("\tjobjectArray result=env->NewObjectArray(size, jni_string_class, NULL);");				
				lines.add("\tfor(int i=0; i<size; i++){");
				currentLine = "\tjstring str = " + 
					getTypeMap(String.class).castFromNative("array[i]") + ";";
				lines.add("\t" + currentLine);
				lines.add("\t\tenv->SetObjectArrayElement(result, i, str);");
				lines.add("\t}");
				lines.add("\treturn result;");
				lines.add("}");
			}
			
			return lines.toArray(new String[lines.size()]);
		}
		
		/**
		 * Return the native type without pointer.
		 * @return
		 */
		private String removeNativePointer(){
			return type.isEnum() ? getNativeName() : 
				getNativeName().substring(0, getNativeName().length() -1);
		}
		
		/**
		 * Capitalize an string.
		 * @param string
		 * @return
		 */
		protected static String capitalize(String string){
			if(string.isEmpty())
				return string;
			return string.substring(0, 1).toUpperCase() +
				string.substring(1);
		}
		
		/**
		 * Get implementation for new method.
		 * @param newMethod
		 * @return
		 * @throws SecurityException
		 * @throws NoSuchMethodException
		 */
		protected final String[] getNewStub(Method newMethod) throws SecurityException, NoSuchMethodException{
			Vector<String> lines = new Vector<String>();
			for(String line : getMethodJniPrototype(newMethod))
				lines.addElement(line);
			lines.add("{");
			lines.add("\treturn (jlong) new " + getNativeName() + "();");
			lines.add("}");
			return lines.toArray(new String[lines.size()]);
		}
		
		/**
		 * Get implementation for finalize method.
		 * @param deleteMethod
		 * @return
		 * @throws SecurityException
		 * @throws NoSuchMethodException
		 */
		protected final String[] getFinalizeStub(Method deleteMethod) throws SecurityException, NoSuchMethodException{
			Vector<String> lines = new Vector<String>();
			for(String line : getMethodJniPrototype(deleteMethod))
				lines.addElement(line);
			lines.add("{");
			lines.add("\tdelete (" + getNativeName() + "*)"+ getNativeObject(type) + ";");
			lines.add("}");
			return lines.toArray(new String[lines.size()]);
		}
		
		/**
		 * Get implementation for LoadEnumClass method.
		 * @return
		 */
		protected final String[] getLoadEnumClassStub(){
			Vector<String> lines = new Vector<String>();
			lines.addElement("if(" + type.getSimpleName() + "Class)");
			lines.addElement("\tenv->DeleteGlobalWeakRef(" + type.getSimpleName() + "Class);");
			lines.addElement(type.getSimpleName() + "Class = env->NewWeakGlobalRef(env->GetObjectClass(obj));");
			lines.addElement("");
			lines.addElement("if(" + type.getSimpleName() + "ordinalMethod)");
			lines.addElement("\tenv->DeleteGlobalWeakRef(" + type.getSimpleName() + "ordinalMethod);");
			lines.addElement(type.getSimpleName() + 
					"ordinalMethod = env->NewWeakGlobalRef(env->GetMethodID(" + 
					type.getSimpleName() +
					"Class, \"ordinal\", \"()I\"));");
			lines.addElement("");
			lines.addElement(type.getSimpleName() +
					"ValuesMethod = env->NewWeakGlobalRef(env->GetMethodID(" + 
					type.getSimpleName() + 
					"Class, \"values\", \"[" +
					getSignature() + 
					"\");");
			lines.addElement("");
			lines.addElement("jobjectArray values = (jobjectArray)env->CallStaticObjectMethod(" + type.getSimpleName() + "Class, " + type.getSimpleName() + "ValuesMethod);");
			lines.addElement("");
			lines.addElement("jint length = env->GetArrayLength(values);");
			lines.addElement("");
			lines.addElement("for(int i=0; i < length; i++){");
			lines.addElement("\tif(" + type.getSimpleName() + "References[i])");
			lines.addElement("\t\tenv->DeleteGlobalWeakRef(" + type.getSimpleName() + "References[value]);");
			lines.addElement("\t" + type.getSimpleName() + "References[i] = env->NewWeakGlobalRef(env->GetObjectArrayElement(values, i));");
			lines.addElement("}");
			return lines.toArray(new String[lines.size()]);
		}
		
		/**
		 * Get implementation for LoadEnumClass method.
		 * @return
		 */
		protected final String[] getLoadClassStub(){
			Vector<String> lines = new Vector<String>();
			lines.addElement("if(" + type.getSimpleName() + "Class)");
			lines.addElement("\tenv->DeleteGlobalWeakRef(" + type.getSimpleName() + "Class);");
			lines.addElement(type.getSimpleName() + "Class = env->NewWeakGlobalRef(clazz);");
			lines.addElement("");
			lines.addElement("if(" + type.getSimpleName() + "Pointer)");
			lines.addElement("\tenv->DeleteGlobalWeakRef(" + type.getSimpleName() + "Pointer);");
			lines.addElement(type.getSimpleName() + "Class = env->NewWeakGlobalRef(env->GetFiedlID(clazz, \"pointer\", \"J\"));");
			return lines.toArray(new String[lines.size()]);
		}
	}
	
	/**
	 * Map of Class to type map.
	 */
	private static final Map<Class<?>, TypeMap> TYPE_MAPS;
	
	/**
	 * This are the primitive, array of primitives, String, Array of String, Object,
	 * Array of Object, and Exception.
	 * maps.
	 */
	private static final TypeMap[] KOWN_MAPS =
	{
		// Void pseudo-type
		new TypeMap(void.class, "V", "void", "void"),
		
		// Basic type
		new TypeMap(char.class, "C", "char", "jchar"),
		new TypeMap(byte.class, "B", "byte", "jbyte"),
		new TypeMap(short.class, "S", "short", "jshort"),
		new TypeMap(int.class, "I", "int", "jint"),
		new TypeMap(long.class, "J", "long", "jlong"),
		new TypeMap(float.class, "F", "float", "jfloat"),
		new TypeMap(double.class, "D", "double", "jdouble"),
		new TypeMap(boolean.class, "Z", "bool", "jboolean"),
		
		// Basic array type
		new TypeMap(char[].class, "[C", "vector<char>*", "jcharArray"),
		new TypeMap(byte[].class, "[B", "vector<byte>*", "jbyteArray"),
		new TypeMap(short[].class, "[S", "vector<short>*", "jshortArray"),
		new TypeMap(int[].class, "[I", "vector<int>*", "jintArray"),
		new TypeMap(long[].class, "[J", "vector<long>*", "jlongArray"),
		new TypeMap(float[].class, "[F", "vector<float>*", "jfloatArray"),
		new TypeMap(double[].class, "[D", "vector<double>*", "jdoubleArray"),
		new TypeMap(boolean[].class, "[Z", "vector<bool>*", "jbooleanArray"),
		
		// String types
		new TypeMap(String.class, "Ljava/lang/String;", "string*", "jstring"),
		new TypeMap(String[].class, "[Ljava/lang/String", "vector<string*>*", "jobjectArray"),
		
		// Object types
		new TypeMap(Object.class, "Ljava/lang/Object;", "(viod*)", "jobject"),
		new TypeMap(Object[].class, "[Ljava/lang/Object;", "vector<void*>*", "jobjectArray"),
		
		// Exception types
		new TypeMap(Throwable.class, "Ljava/lang/Throwable", "Throwable*", "jthrowable"),
		new TypeMap(NativeException.class, "Lsetvor/npc/NativeException", "NativeException*", "jthrowable")
	};
	
	/**
	 * This static method initializes the TYPE_MAPS and add to the 
	 * TYPE_MAPS map.
	 */
	static{
		TYPE_MAPS = new Hashtable<Class<?>, TypeMap>();
		for(TypeMap kwonMap : KOWN_MAPS)
			TYPE_MAPS.put(kwonMap.getType(), kwonMap);		
	}
	
	/**
	 * Gets the Type map of 
	 * @param clazz
	 * @return the respective type amp
	 */
	public static final TypeMap getTypeMap(Class<?> clazz){
		if(TYPE_MAPS.containsKey(clazz))
			return TYPE_MAPS.get(clazz);
		TypeMap newTypeMap = new TypeMap(clazz);
		TYPE_MAPS.put(clazz, newTypeMap);
		return newTypeMap;
	}
	
	/**
	 * Gets the signature of method without overrides.
	 * @param method: the method that return the signature.
	 * @return Method signature.
	 */
	public static String getMethodSignature(Method method){
		String parameters = "";
		for(Class<?> clazz: method.getParameterTypes()){
			TypeMap typeMap = getTypeMap(clazz);
			parameters += typeMap.getSignature();
		}
		return "(" + parameters + ")" + getTypeMap(method.getReturnType()).getSignature();		
	}
	
	/**
	 * Gets the override suffix of one method.
	 * @param method
	 * @return Method signature.
	 */
	public static String getMethodNativeName(Method method){
		Class<?> clazz =method.getDeclaringClass();
		int countOverrides = 0;
		for(Method otherMethod : clazz.getDeclaredMethods())
			if(method.getName().equals(otherMethod.getName()))
				countOverrides++;
		
		return "java_" +
			getTypeMap(clazz).getNativeFileName() + "_" +
			method.getName() +
			(countOverrides > 1 ? getMethodOverrideSuffix(method) : "");
	}
	
	/**
	 * Gets the override suffix for a method.
	 * @param method for get the override suffix.
	 * @return overide suffix.
	 */
	public static String getMethodOverrideSuffix(Method method){
		String overrideSuffix = "";
		
		if(method.getParameterTypes().length == 0)
			return overrideSuffix;
		
		for(Class<?> clazz : method.getParameterTypes()){
			TypeMap typeMap = getTypeMap(clazz);
			overrideSuffix +=  typeMap.getSignature();
		}
		
		overrideSuffix = overrideSuffix.replaceAll(Pattern.quote("_"), "_1");
		overrideSuffix = overrideSuffix.replaceAll(Pattern.quote(";"), "_2");
		overrideSuffix = overrideSuffix.replaceAll(Pattern.quote("["), "_3");
		
		for(char character : overrideSuffix.toCharArray())
			if(Character.isUnicodeIdentifierPart(character))
				overrideSuffix.replace(Pattern.quote("" + character), Character.toString(character));
		
		return "__" + overrideSuffix;
	}

	/**
	 * Gets the JNI stub prototype method.
	 * @param method
	 * @return JNI stub prototype method.
	 */
	public static final String[] getMethodJniPrototype(Method method) {
		Vector<String> lines = new Vector<String>();
		
		// JNIEXPORT <return type> JNICALL <name>
		String currentLine = "JNIEXPORT " +
			getTypeMap(method.getReturnType()).getJniType() +
			" JNICALL " +
			getMethodNativeName(method);
		lines.addElement(currentLine);
		
		lines.addElement("(");
		
		// (JNIEnv *env,
		lines.addElement("\tJNIEnv *env,");
		
		// jclass clazz | jobject obj
		currentLine = Modifier.isStatic(method.getModifiers()) ? 
			"jclass clazz" :
			"jobject obj";
		
		// paramters
		int paramterNumber = 0;		
		for(Class<?> clazz : method.getParameterTypes()){
			lines.addElement("\t" + currentLine + ",");
			currentLine = getTypeMap(clazz).getJniType() + " arg" + paramterNumber++;
		}
		
		lines.addElement("\t" + currentLine);
		
		lines.addElement(")");
		
		return lines.toArray(new String[lines.size()]);
	}

	/**
	 * Get the native function prototype.
	 * @param method
	 * @return native function prototype.
	 */
	public static final String[] getMethodNativePrototype(Method method){
		Vector<String> lines = new Vector<String>();
		String currentLine = "public: " +
			(Modifier.isStatic(method.getModifiers()) ? "static " : "") +
			( method.getReturnType().isPrimitive() ? 
				getTypeMap(method.getReturnType()).getNativeName() :
				getTypeMap(method.getReturnType()).removeNativePointer()) + " " +
			method.getName();
		
		// Parameters
		if(method.getParameterTypes().length > 0){
			lines.addElement(currentLine);
			lines.addElement("(");
			boolean isFirst = true;
			int paramterNumber = 0;
			for(Class<?> clazz : method.getParameterTypes()){
				if(!isFirst)
					lines.addElement("\t" + currentLine + ",");
				currentLine = getTypeMap(clazz).getNativeName() +
					" arg" + paramterNumber++;
				isFirst = false;
			}
			lines.addElement("\t" + currentLine);
			lines.addElement(")");
		}else
			lines.addElement(currentLine+"()");
		
		currentLine = "";
		
		// Exceptions
		if(method.getExceptionTypes().length > 0){
			currentLine = "/* throws ";
			boolean isFirst = true;
			for(Class<?> exceptionType : method.getExceptionTypes()){
				TypeMapper.TypeMap exceptionTypeMap = TypeMapper.getTypeMap(exceptionType);
				currentLine += (!isFirst ?  "" : ", ") + exceptionTypeMap.getCompleteNativeName();
				isFirst = false;
			}
			currentLine += " */";
		}
		
		if(!currentLine.isEmpty())
			lines.addElement(currentLine);
		
		return lines.toArray(new String[lines.size()]);
	}	
	
	/**
	 * The name of the get native object method.
	 */
	public static final String getNativeObject(Class<?> clazz){
		return "env->GetLongField(obj, " + getTypeMap(clazz).getNativeName() +"Pointer)";
	}
	
	/**
	 * Generate the function stub for this method without try statement; 
	 * @param method
	 * @return a method stub.
	 */
	public static final String[] getMethodStubBody(Method method){
		Vector<String> lines = new Vector<String>();
		final boolean isVoidReturn = void.class.equals(method.getReturnType());
		final boolean isStatic = Modifier.isStatic(method.getModifiers());
		final boolean isEnum = method.getDeclaringClass().isEnum();
		final boolean isEnumReturn = method.getReturnType().isEnum();
		TypeMap returnTypeMap = getTypeMap(method.getReturnType());
		TypeMap declarationTypeMap = getTypeMap(method.getDeclaringClass());
		String currentLine =  "";
		
		if(isStatic){
		}else if (isEnum){
			currentLine =
				declarationTypeMap.getNativeEnumClassName() + " nativeObject = ";
			lines.addElement(currentLine);
			currentLine = "\t" +
				declarationTypeMap.getNativeEnumClassName() + TypeMap.nameSpaceSeparator + 
				"values[env->CallIntMethod(obj, " +
				method.getReturnType().getSimpleName() + "OrdinalMethod)];";
			lines.addElement(currentLine);
		}else{
			currentLine = declarationTypeMap.getNativeName() + "* " +
				"nativeObject = " +
				"(" +declarationTypeMap.getNativeName() + "*)" + 
				getNativeObject(method.getDeclaringClass()) + ";";
			lines.addElement(currentLine);
		}
				
		// result
		currentLine = isVoidReturn ? "" :
			returnTypeMap.getNativeName() + " result = ";

		currentLine +=
			isStatic ? declarationTypeMap.getNativeName() + 
				TypeMap.nameSpaceSeparator +
				method.getName() :
			"nativeObject" + (isEnum ? "." : "->") + method.getName();
		
		// Parameters
		if(method.getParameterTypes().length > 0){
			lines.addElement(currentLine);
			lines.addElement("(");
			boolean isFirst = true;
			int paramterNumber = 0;
			for(Class<?> clazz : method.getParameterTypes()){
				String parameter = "arg" + paramterNumber++;
				if(!isFirst)
					lines.addElement("\t" + currentLine + ",");
				currentLine = getTypeMap(clazz).castToNative(parameter);
				if(!clazz.isPrimitive())
					currentLine = parameter + " ? &" + currentLine + " : NULL";
				isFirst = false;
			}
			lines.addElement("\t" + currentLine);
			lines.addElement(");");
		}else
			lines.addElement(currentLine+"();");
		
		if(!isVoidReturn){
			currentLine = "return " +
				(!(returnTypeMap.getType().isPrimitive() || isEnumReturn )? "result ? " : "") +
				(isEnumReturn ? "result > -1 && result < " + 
						(method.getDeclaringClass().getEnumConstants().length + 1) + " ? " : "" ) +
				returnTypeMap.castFromNative("result") +
				(!(returnTypeMap.getType().isPrimitive())? " : NULL" : "") +
				";";
			lines.addElement(currentLine);
		}
		
		return lines.toArray(new String[lines.size()]);
	}

	/**
	 * Generate the function stub for this method with try statement; 
	 * @param method
	 * @return a method stub.
	 */
	public static final String[] getMethodStubBodyTry(Method method){
		Vector<String> lines = new Vector<String>();
		lines.addElement("try{");
		
		for(String line : getMethodStubBody(method))
			lines.addElement("\t" + line);
		
		lines.addElement("}catch(Exception& e){");
		
		lines.addElement("\tjclass exceptionClass = env->FindClass(e.getJavaClassName());");
		lines.addElement("\tif(!exceptionClass)");
		lines.addElement("\t\treturn NULL;");
		lines.addElement("\tenv->ThrowNew(exceptionClass, e.getMessageToJava());");
		lines.addElement("\treturn NULL;");
		
		lines.addElement("}");
		return lines.toArray(new String[lines.size()]);
	}
	
	/**
	 * Generate JNI calls stubs for entire class.
	 * @param clazz
	 * @return
	 * @throws SecurityException
	 * @throws NoSuchMethodException
	 */
	@SuppressWarnings("unchecked")
	public static final String[] getClassStubs(Class<? extends NativeWriteable> clazz)
		throws SecurityException, NoSuchMethodException{
		Vector<String> lines = new Vector<String>();
		TypeMap typeMap = getTypeMap(clazz);
		
		// Includes
		lines.addElement("//File \"" + typeMap.getNativeFileName() +".cpp\"");
		lines.addElement("#include \"" + typeMap.getNativeFileName() +".h\"");
		lines.addElement("#include \"" + clazz.getSimpleName() +".h\"");
		lines.addElement("");
		lines.addElement("#include <string>");
		lines.addElement("#include <vector>");
		lines.addElement("#include <jni.h>");
		lines.addElement("#include <npc.h>");
		lines.addElement("");
		
		// Using name space
		lines.addElement("using namespace std;");
		lines.addElement("using namespace setvor::npc;");
		lines.addElement("using namespace " + typeMap.getNameSpace() + ";");
		
		// Casts types
		for(TypeMap basicTypeMap : KOWN_MAPS){
			if(basicTypeMap.getCasToNativeStub().length == 0)
				continue;
			lines.addElement("");
			for(String line : basicTypeMap.getCasToNativeStub())
				lines.addElement(line);
			lines.addElement("");
			for(String line : basicTypeMap.getCasFromNativeStub())
				lines.addElement(line);
		}
		
		lines.addElement("");
		
		if(clazz.isEnum()){
			if(!NativeEnum.class.isAssignableFrom(clazz))
				throw new IllegalArgumentException(clazz.getName() +
						" is enum but do not implement " + NativeEnum.class.getName());
			for(String line : getEnumReferencesDeclaration((Class<? extends Enum<? extends NativeEnum>>)clazz))
				lines.addElement(line);

		}else{
			for(String line : getClassReferencesDeclaration(clazz))
				lines.addElement(line);
			
		}
		lines.addElement("");
		
		boolean hasNativeFinalize = false;
		
		for(Method method : clazz.getDeclaredMethods()){
			
			if(method.getParameterTypes().length == 0){
				if(method.getName().equals("nativeNew")){
					if(!Modifier.isNative(method.getModifiers()))
						throw new IllegalArgumentException("Method nativeNew() must be native." );
					if(!Modifier.isProtected(method.getModifiers()))
						throw new IllegalArgumentException("Method nativeNew() must be protected." );
					for(String line : typeMap.getNewStub(method))
						lines.addElement(line);
					lines.addElement("");
					continue;
				}
				if(method.getName().equals("nativeFinalize")){
					if(!Modifier.isNative(method.getModifiers()))
						throw new IllegalArgumentException("Method nativeFinalize() must be native." );
					if(!Modifier.isProtected(method.getModifiers()))
						throw new IllegalArgumentException("Method nativeFinalize() must be protected." );
					for(String line : typeMap.getFinalizeStub(method))
						lines.addElement(line);
					lines.addElement("");
					continue;
				}
				if(method.getName().equals("nativeLoadClass")){
					if(!(Modifier.isNative(method.getModifiers()) &&
							Modifier.isStatic(method.getModifiers()) &&
							Modifier.isPublic(method.getModifiers())))
						throw new IllegalArgumentException(
								"Method nativeLoadClass() must be public static native." );
					hasNativeFinalize = true;
					for(String line : getMethodJniPrototype(method))
						lines.addElement(line);
					lines.addElement("{");
					for(String line : 
						clazz.isEnum() ? typeMap.getLoadEnumClassStub() : typeMap.getLoadClassStub())
						lines.addElement("\t" + line);
					lines.addElement("");
					
					lines.addElement("}");
					lines.addElement("");
					continue;
				}
			}
			
			if(!Modifier.isNative(method.getModifiers()))
				continue;
			
			for(String line : getMethodJniPrototype(method))
				lines.addElement(line);
			lines.addElement("{");
			for(String line : getMethodStubBodyTry(method))
				lines.addElement("\t" + line);
			lines.addElement("}");
			lines.addElement("");
		}
		
		if(!hasNativeFinalize)
			throw new IllegalArgumentException(clazz.getName() +
				" has not method 'private static native nativeLoadClass();'" );
		
		return lines.toArray(new String[lines.size()]);
	}
	
	/**
	 * Generate the prototype for a native class.
	 * @param clazz
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static final String[] getClassNativePrototype(Class<? extends NativeWriteable> clazz){
		Vector<String> lines = new Vector<String>();
		TypeMap typeMap = getTypeMap(clazz);
		lines.addElement("//File \"" + clazz.getSimpleName() +".h\"");
		lines.addElement("#ifndef __" + typeMap.getNativeFileName().toUpperCase() + "_H__");
		lines.addElement("#define __" + typeMap.getNativeFileName().toUpperCase() + "_H__");
		lines.addElement("");
		lines.addElement("#include <string>");
		lines.addElement("#include <vector>");
		lines.addElement("");
		
		// Using name space
		lines.addElement("using namespace std;");
		lines.addElement("using namespace setvor::npc;");
		lines.addElement("");
		lines.addElement("namespace " + typeMap.getNameSpace() + "{");
		lines.addElement("");
		if(clazz.isEnum()){
			for(String line : getEnumDeclaration((Class<? extends Enum<? extends NativeEnum>>)clazz))
				lines.addElement("\t" + line);
			lines.addElement("");
			for(String line : getEnumNames((Class<? extends Enum<? extends NativeEnum>>)clazz))
				lines.addElement("\t" + line);
			lines.addElement("");
		}
		lines.addElement("\tclass " +
				(clazz.isEnum() ? typeMap.getNativeEnumClassName() : typeMap.getNativeName()) +
				(Throwable.class.isAssignableFrom(clazz) ? " : NativeException" : "") +
				"{");
		
		lines.addElement("");
		if(clazz.isEnum()){				
			lines.addElement("");
			lines.addElement("\t\tprivate: string _name;");
			lines.addElement("\t\tpublic: string name(){");
			lines.addElement("\t\t\treturn _name;");
			lines.addElement("\t\t}");
			lines.addElement("");
			lines.addElement("\t\tprivate: int _ordinal;");
			lines.addElement("\t\tpublic: const int ordinal(){");
			lines.addElement("\t\t\treturn _ordinal;");
			lines.addElement("\t\t}");
			lines.addElement("");
			lines.addElement("\t\tprivate: " +typeMap.getNativeEnumClassName() + 
				"(char* name, int ordinal){");
			lines.addElement("\t\t\tthis->_name = name;");
			lines.addElement("\t\t\tthis->_ordinal = ordinal;");
			lines.addElement("\t\t}");
			lines.addElement("");
			lines.addElement("\t\tprivate: ~" +typeMap.getNativeEnumClassName() + "();");
			lines.addElement("");
			lines.addElement("\t\tpublic: const int length = " +
				clazz.getEnumConstants().length + ";");
			lines.addElement("");
			for(Enum<? extends NativeEnum> constant : 
					(Enum<? extends NativeEnum>[])clazz.getEnumConstants())
				lines.addElement("\t\tpublic: const " +
						typeMap.getNativeEnumClassName() + " " +
						constant.name() + " = " +
						typeMap.getNativeEnumClassName() +
						"(\"" + constant.name() + "\", " + constant.ordinal() + ");");
			lines.addElement("");
			lines.addElement("\t\tpublic: const " + 
				typeMap.getNativeEnumClassName() + " values[] = {");
			String currentLine = "";
			boolean isFirst = true;
			for(Enum<? extends NativeEnum> constant : 
					(Enum<? extends NativeEnum>[])clazz.getEnumConstants()){
				if(!isFirst)
					lines.addElement("\t\t\t" + currentLine + ",");
				isFirst = false;
				currentLine = constant.name();
			}
			lines.addElement("\t\t\t" + currentLine);
			lines.addElement("\t\t};");
			
		}else{
			
			lines.addElement("\t\tpublic: " +typeMap.getNativeName() + "();");
			lines.addElement("\t\tpublic: ~" +typeMap.getNativeName() + "();");
			
		}
		lines.addElement("");
		
		for(Method method : clazz.getDeclaredMethods()){
			if(!Modifier.isNative(method.getModifiers()))
				continue;
			if(method.getParameterTypes().length == 0 && 
					(method.getName().equals("nativeLoadClass") ||
					method.getName().equals("nativeNew") ||
					method.getName().equals("nativeFinalize")))
				continue;
			String[] prototype = getMethodNativePrototype(method);
			int lineNumber = 1;
			for(String line : prototype){
				lines.addElement("\t\t" + line + (lineNumber == prototype.length ? ";" : ""));
				lineNumber++;
			}
			lines.addElement("");
		}
		
		lines.addElement("\t};");
		
		lines.addElement("");
		lines.addElement("};");
		lines.addElement("#endif");
		
		return lines.toArray(new String[lines.size()]);
	}

	/**
	 * Get the native declaration of enum type.
	 * @param clazz
	 * @return the native declaration of enum type
	 */
	public static final String[] getEnumDeclaration(Class<? extends Enum<? extends NativeEnum>> clazz){
		Vector<String> lines = new Vector<String>();
		lines.addElement("typedef enum{");
		boolean isFirst = true;
		String currentLine = "";
		for(Enum<? extends NativeEnum> enumValue: clazz.getEnumConstants()){
			if(!isFirst)
				lines.addElement("\t"+currentLine);				
			isFirst=false;
			currentLine = enumValue.name();
		}
		lines.addElement("\t"+currentLine);
		lines.addElement("}" + clazz.getSimpleName() +";");
		return lines.toArray(new String[lines.size()]);
	}
	
	/**
	 * Get an array of char* with the name of enum.
	 * @param clazz
	 * @return array of char*
	 */
	public static final String[] getEnumNames(Class<? extends Enum<? extends NativeEnum>> clazz){
		Vector<String> lines = new Vector<String>();
		lines.addElement("const char* " + clazz.getSimpleName() + "Names[] = {");
		boolean isFirst = true;
		String currentLine = "";
		for(Enum<? extends NativeEnum> enumValue: clazz.getEnumConstants()){
			if(!isFirst)
				lines.addElement("\t" + currentLine + ",");
			isFirst=false;
			currentLine = "\"" + enumValue.name() + "\"";
		}
		lines.addElement("\t"+currentLine);
		lines.addElement("};");
		return lines.toArray(new String[lines.size()]);
	}

	/**
	 * Get the variables for work with enum in the native enviroment.
	 * @param clazz
	 * @return
	 */
	public static final String[] getEnumReferencesDeclaration
		(Class<? extends Enum<? extends NativeEnum>> clazz){
		Vector<String> lines = new Vector<String>();
		lines.add("static jobject " +
			clazz.getSimpleName() +
			"Refernces[" + clazz.getEnumConstants().length +"];");
		lines.add("static jmethodID " + clazz.getSimpleName() + "OrdinalMethod;");
		lines.add("static jclass " + clazz.getSimpleName() + "Class;");
		return lines.toArray(new String[lines.size()]);
	}
	
	/**
	 * Get the variables for work with class in the native enviroment.
	 * @param clazz
	 * @return
	 */
	public static final String[] getClassReferencesDeclaration
		(Class<?> clazz){
		Vector<String> lines = new Vector<String>();
		lines.add("static jmethodID " + clazz.getSimpleName() + "Pointer;");
		lines.add("static jclass " + clazz.getSimpleName() + "Class;");
		return lines.toArray(new String[lines.size()]);
	}

	
}